package ArithmeticLibrary.Elements;


import java.math.BigInteger;

import ArithmeticLibrary.Sets.Field;
import ArithmeticLibrary.Sets.PrimeField;
import ByteTree.ByteTree;
import ByteTree.Leaf;
import ByteTree.Node;

/**
 * This class represents a mathematical prime field element.
 * This class supports the operations of a field: 
 * 	addition, multiplication, power, additive inverse and multiplicative inverse.
 * 
 * @author Lea Golan
 *
 */
public class ElementPrimeField extends ElementField {

	private PrimeField set;//a reference to the set the element belongs to
	private java.math.BigInteger val;//the numeric value of the element
	
	/**
	 * constructs a new element according to the given parameters
	 * 
	 * @param value - the element value
	 * @param field - the field the element belongs to
	 */
	public ElementPrimeField(java.math.BigInteger value, PrimeField field){
		this.set = field;
		this.val = this.set.getP().getValModulo(value);
	}
	
	/**
	 * The construction fails if message is not of the described structure.
	 * @param message - is of the form: leaf(bytes_k (a)) where 
	 * 		a is the value of the element, 
	 * 		k is the smallest integer bigger than the logarithm in base two of the prime field order
	 * @param field - the field the element belongs to
	 * @throws Exception - if message is not properly formatted 
	 */
	public ElementPrimeField(ByteTree message, PrimeField field) throws Exception{
		this.set = field;
		try {
			this.val = checkTree(message);
		} catch (Exception e) {
			throw new Exception("Build Error in ElementPrimeField:\n"+e.getMessage());
		}
	}

	private BigInteger checkTree(ByteTree tree) throws Exception {
		Node root = tree.getRoot();
		if (root.isLeaf() == false){
			throw new Exception("Build Error in ElementPrimeField: ByteTree structure is inconsistent with ElementPrimeField");
		}
		BigInteger temp = new BigInteger(((Leaf) root).getData());
		return this.set.getP().getValModulo(temp);
	}

	/**
	 * Compare this to obj by checking equivalence of the value and the set.
	 */
	@Override
	public boolean equals(Object object) {
		if (object == this){
            return true;
        }
        if (!(object instanceof ElementPrimeField)){
            return false;
        }
        
        ElementPrimeField other = (ElementPrimeField) object;
        return ( (this.val.equals(other.val)) && (this.set.equals(other.set)));
	}
	
	/**
	 * Checks if this structure is consistent with obj structure. 
	 * it returns true iff obj is an instance of the same class. 
	 * 
	 * @param obj an object we want to check with our structure
	 * @return true iff this and obj are of the same structure
	 */
	@Override
	public boolean checkStructure(Object obj) {
		return (obj instanceof ElementPrimeField);
	}

	/**
	 * Returns the element representation number in the field.
	 *
	 * @return element representation number in the field
	 */
	@Override
	public java.math.BigInteger getElementValue() {
		return this.val;
	}

	/**
	 * sets the element value to newValue
	 * 
	 * @param newValue - new numeric value representing the element
	 */
	public void setElementValue(java.math.BigInteger newValue) {
		this.val = this.set.getP().getValModulo(newValue);
	}
	
	/**
	 * @return the field which contains this element
	 */
	@Override
	public Field getField() {
		return this.set;
	}
	
	/**
	 * Returns a new field element which is the result of calculating this^exponent.
	 * 
	 * @param exponent - an exponent
	 * @return a new ElementField that holds the value this^exponent
	 */
	@Override
	public ElementPrimeField power(java.math.BigInteger exponent) {
		BigInteger result = set.getP().powModulo(val, exponent);
		return new ElementPrimeField(result, set);
	}
	
	/**
	 * Returns a new field element which is the result of adding the current element 
	 * to the other element (both elements should be in the same field).
	 *  
	 * @param other - the element we want to add
	 * @return a new element that is the result of adding this to other 
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementPrimeField add(ElementField other) throws Exception {
		BigInteger result;
		try {
			result = set.getP().addModulo(val, checkElement(other));
			return new ElementPrimeField(result, set);
		} catch (Exception e) {
			throw new Exception("error in ElementPrimeField.add:\n"+e.getMessage());
		}
	}

	/**
	 * Returns a new field element which is the result of multiplying the current element 
	 * with the other element (both elements should be in the same field).
	 * 
	 * @param other - the element we want to multiply
	 * @return a new element that is the result of multiplying this to other 
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementPrimeField multiply(ElementField other) throws Exception {
		BigInteger result;
		try {
			result = set.getP().multiplyModulo(val, checkElement(other));
			return new ElementPrimeField(result, set);
		} catch (Exception e) {
			throw new Exception("error in ElementPrimeField.multiply:\n"+e.getMessage());
		}
	}
	
	/**
	 * @return a new field element which is the multiplicative inverse of this
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementPrimeField multInverse() throws Exception {
		BigInteger result;
		try {
			result = set.getP().multInverseModulo(val);
			return new ElementPrimeField(result, set);
		} catch (Exception e) {
			throw new Exception("error in ElementPrimeField.multInverse:\n"+e.getMessage());
		}
	}
	
	/**
	 * @return a new field element which is the additive inverse of this
	 */
	@Override
	public ElementField additiveInverse() {
		BigInteger result = set.getP().addInverseModulo(val);
		return new ElementPrimeField(result, set);
	}	

	private java.math.BigInteger checkElement(ElementField other) throws Exception{
		if (!(other instanceof ElementPrimeField)){
			throw new Exception("Element must be a instance of ElementPrimefield");
		}
		ElementPrimeField otherElem = (ElementPrimeField) other;
		if ((this.set.equals(otherElem.set)) == false) {
			throw new Exception("Elements must belong to the same field");
		}
		return otherElem.val;
	}
	
}
